<!DOCTYPE html>
<html>

<head>
  <title>@aria-owns attribute testing</title>

  <link rel="stylesheet" type="text/css"
        href="chrome://mochikit/content/tests/SimpleTest/test.css" />

  <script type="application/javascript"
          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>

  <script type="application/javascript"
          src="../common.js"></script>
  <script type="application/javascript"
          src="../role.js"></script>
  <script type="application/javascript"
          src="../events.js"></script>

  <script type="application/javascript">

    ////////////////////////////////////////////////////////////////////////////
    // Invokers
    ////////////////////////////////////////////////////////////////////////////

    function changeARIAOwns()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_button")),
        // no hide for t1_subdiv because it is contained by hidden t1_checkbox
        new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")),
        new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")),
        new invokerChecker(EVENT_SHOW, getNode("t1_button")),
        new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
        new invokerChecker(EVENT_REORDER, getNode("t1_container"))
      ];

      this.invoke = function setARIAOwns_invoke()
      {
        // children are swapped by ARIA owns
        var tree =
          { SECTION: [
              { CHECKBUTTON: [
                { SECTION: [] }
              ] },
              { PUSHBUTTON: [ ] }
          ] };
        testAccessibleTree("t1_container", tree);

        getNode("t1_container").
          setAttribute("aria-owns", "t1_button t1_subdiv");
      }

      this.finalCheck = function setARIAOwns_finalCheck()
      {
        // children are swapped again, button and subdiv are appended to
        // the children.
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] }, // checkbox, native order
              { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
              { SECTION: [ ] } // subdiv from the subtree, ARIA owned
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function setARIAOwns_getID()
      {
        return "Change @aria-owns attribute";
      }
    }

    function removeARIAOwns()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_button")),
        new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
        new orderChecker(),
        new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")),
        new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
        new orderChecker(),
        new invokerChecker(EVENT_REORDER, getNode("t1_container")),
        new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox"))
      ];

      this.invoke = function removeARIAOwns_invoke()
      {
        getNode("t1_container").removeAttribute("aria-owns");
      }

      this.finalCheck = function removeARIAOwns_finalCheck()
      {
        // children follow the DOM order
        var tree =
          { SECTION: [
              { PUSHBUTTON: [ ] },
              { CHECKBUTTON: [
                  { SECTION: [] }
              ] }
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function removeARIAOwns_getID()
      {
        return "Remove @aria-owns attribute";
      }
    }

    function setARIAOwns()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_button")),
        new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
        new invokerChecker(EVENT_SHOW, getNode("t1_button")),
        new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
        new invokerChecker(EVENT_REORDER, getNode("t1_container"))
      ];

      this.invoke = function setARIAOwns_invoke()
      {
        getNode("t1_container").
          setAttribute("aria-owns", "t1_button t1_subdiv");
      }

      this.finalCheck = function setARIAOwns_finalCheck()
      {
        // children are swapped again, button and subdiv are appended to
        // the children.
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] }, // checkbox
              { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
              { SECTION: [ ] } // subdiv from the subtree, ARIA owned
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function setARIAOwns_getID()
      {
        return "Set @aria-owns attribute";
      }
    }

    function addIdToARIAOwns()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_group")),
        new invokerChecker(EVENT_SHOW, getNode("t1_group")),
        new invokerChecker(EVENT_REORDER, document)
      ];

      this.invoke = function addIdToARIAOwns_invoke()
      {
        getNode("t1_container").
          setAttribute("aria-owns", "t1_button t1_subdiv t1_group");
      }

      this.finalCheck = function addIdToARIAOwns_finalCheck()
      {
        // children are swapped again, button and subdiv are appended to
        // the children.
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] }, // t1_checkbox
              { PUSHBUTTON: [ ] }, // button, t1_button
              { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
              { GROUPING: [ ] } // group from outside, t1_group
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function addIdToARIAOwns_getID()
      {
        return "Add id to @aria-owns attribute value";
      }
    }

    function appendEl()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_SHOW, getNode, "t1_child3"),
        new invokerChecker(EVENT_REORDER, getNode("t1_container"))
      ];

      this.invoke = function appendEl_invoke()
      {
        var div = document.createElement("div");
        div.setAttribute("id", "t1_child3");
        div.setAttribute("role", "radio")
        getNode("t1_container").appendChild(div);
      }

      this.finalCheck = function appendEl_finalCheck()
      {
        // children are invalidated, they includes aria-owns swapped kids and
        // newly inserted child.
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox
              { RADIOBUTTON: [ ] }, // new explicit, t1_child3
              { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
              { SECTION: [ ] }, // ARIA owned, t1_subdiv
              { GROUPING: [ ] } // ARIA owned, t1_group
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function appendEl_getID()
      {
        return "Append child under @aria-owns element";
      }
    }

    function removeEl()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode, "t1_checkbox"),
        new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"),
        new invokerChecker(EVENT_REORDER, getNode("t1_container"))
      ];

      this.invoke = function removeEl_invoke()
      {
        // remove a container of t1_subdiv
        getNode("t1_span").parentNode.removeChild(getNode("t1_span"));
      }

      this.finalCheck = function removeEl_finalCheck()
      {
        // subdiv should go away
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] }, // explicit, t1_checkbox
              { RADIOBUTTON: [ ] }, // explicit, t1_child3
              { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
              { GROUPING: [ ] } // ARIA owned, t1_group
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function removeEl_getID()
      {
        return "Remove a container of ARIA owned element";
      }
    }

    function removeId()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_group")),
        new invokerChecker(EVENT_SHOW, getNode("t1_group")),
        new invokerChecker(EVENT_REORDER, document)
      ];

      this.invoke = function removeId_invoke()
      {
        getNode("t1_group").removeAttribute("id");
      }

      this.finalCheck = function removeId_finalCheck()
      {
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] },
              { RADIOBUTTON: [ ] },
              { PUSHBUTTON: [ ] } // ARIA owned, t1_button
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function removeId_getID()
      {
        return "Remove ID from ARIA owned element";
      }
    }

    function setId()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")),
        new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")),
        new invokerChecker(EVENT_REORDER, document)
      ];

      this.invoke = function setId_invoke()
      {
        getNode("t1_grouptmp").setAttribute("id", "t1_group");
      }

      this.finalCheck = function setId_finalCheck()
      {
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] },
              { RADIOBUTTON: [ ] },
              { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
              { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp
          ] };
        testAccessibleTree("t1_container", tree);
      }

      this.getID = function setId_getID()
      {
        return "Set ID that is referred by ARIA owns";
      }
    }

    /**
     * Remove an accessible DOM element containing an element referred by
     * ARIA owns.
     */
    function removeA11eteiner()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, getNode("t2_container1"))
      ];

      this.invoke = function removeA11eteiner_invoke()
      {
        var tree =
          { SECTION: [
              { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned'
          ] };
        testAccessibleTree("t2_container1", tree);

        getNode("t2_container2").removeChild(getNode("t2_container3"));
      }

      this.finalCheck = function removeA11eteiner_finalCheck()
      {
        var tree =
          { SECTION: [
          ] };
        testAccessibleTree("t2_container1", tree);
      }

      this.getID = function removeA11eteiner_getID()
      {
        return "Remove an accessible DOM element containing an element referred by ARIA owns";
      }
    }

    /**
     * Steal an element from other ARIA owns element. This use case guarantees
     * that result of setAttribute/removeAttribute doesn't depend on their order.
     */
    function stealFromOtherARIAOwns()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
      ];

      this.invoke = function stealFromOtherARIAOwns_invoke()
      {
        getNode("t3_container2").setAttribute("aria-owns", "t3_child");
      }

      this.finalCheck = function stealFromOtherARIAOwns_finalCheck()
      {
        var tree =
          { SECTION: [
          ] };
        testAccessibleTree("t3_container1", tree);

        tree =
          { SECTION: [
            { CHECKBUTTON: [
            ] }
          ] };
        testAccessibleTree("t3_container2", tree);
      }

      this.getID = function stealFromOtherARIAOwns_getID()
      {
        return "Steal an element from other ARIA owns element";
      }
    }

    function appendElToRecacheChildren()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, getNode("t3_container2"))
      ];

      this.invoke = function appendElToRecacheChildren_invoke()
      {
        var div = document.createElement("div");
        div.setAttribute("role", "radio")
        getNode("t3_container2").appendChild(div);
      }

      this.finalCheck = function appendElToRecacheChildren_finalCheck()
      {
        var tree =
          { SECTION: [
          ] };
        testAccessibleTree("t3_container1", tree);

        tree =
          { SECTION: [
            { RADIOBUTTON: [ ] },
            { CHECKBUTTON: [ ] } // ARIA owned
          ] };
        testAccessibleTree("t3_container2", tree);
      }

      this.getID = function appendElToRecacheChildren_getID()
      {
        return "Append a child under @aria-owns element to trigger children recache";
      }
    }

    function showHiddenElement()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, getNode("t4_container1"))
      ];

      this.invoke = function showHiddenElement_invoke()
      {
        var tree =
          { SECTION: [
            { RADIOBUTTON: [] }
          ] };
        testAccessibleTree("t4_container1", tree);

        getNode("t4_child1").style.display = "block";
      }

      this.finalCheck = function showHiddenElement_finalCheck()
      {
        var tree =
          { SECTION: [
            { CHECKBUTTON: [] },
            { RADIOBUTTON: [] }
          ] };
        testAccessibleTree("t4_container1", tree);
      }

      this.getID = function showHiddenElement_getID()
      {
        return "Show hidden ARIA owns referred element";
      }
    }

    function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList)
    {
      this.eventSeq = [];
      for (var id of aIdList) {
        this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id)));
      }

      for (var id of aIdList) {
        this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id)));
      }
      this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer)));

      this.invoke = function rearrangeARIAOwns_invoke()
      {
        getNode(aContainer).setAttribute("aria-owns", aAttr);
      }

      this.finalCheck = function rearrangeARIAOwns_finalCheck()
      {
        var tree = { SECTION: [ ] };
        for (var role of aRoleList) {
          var ch = {};
          ch[role] = [];
          tree["SECTION"].push(ch);
        }
        testAccessibleTree(aContainer, tree);
      }

      this.getID = function rearrangeARIAOwns_getID()
      {
        return `Rearrange @aria-owns attribute to '${aAttr}'`;
      }
    }

    function removeNotARIAOwnedEl(aContainer, aChild)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, aContainer)
      ];

      this.invoke = function removeNotARIAOwnedEl_invoke()
      {
        var tree = {
          SECTION: [
            { TEXT_LEAF: [ ] },
            { GROUPING: [ ] }
          ]
        };
        testAccessibleTree(aContainer, tree);

        getNode(aContainer).removeChild(getNode(aChild));
      }

      this.finalCheck = function removeNotARIAOwnedEl_finalCheck()
      {
        var tree = {
          SECTION: [
            { GROUPING: [ ] }
          ]
        };
        testAccessibleTree(aContainer, tree);
      }

      this.getID = function removeNotARIAOwnedEl_getID()
      {
        return `remove not ARIA owned child`;
      }
    }

    function setARIAOwnsOnElToRemove(aParent, aChild)
    {
      this.eventSeq = [
        new invokerChecker(EVENT_HIDE, getAccessible(aParent))
      ];

      this.invoke = function setARIAOwnsOnElToRemove_invoke()
      {
        getNode(aChild).setAttribute("aria-owns", "no_id");
        getNode(aParent).removeChild(getNode(aChild));
        getNode(aParent).parentNode.removeChild(getNode(aParent));
      }

      this.getID = function setARIAOwnsOnElToRemove_getID()
      {
        return `set ARIA owns on an element, and then remove it, and then remove its parent`;
      }
    }

    /**
     * Set ARIA owns on inaccessible span element that contains
     * accessible children. This will move children from the container for
     * the span.
     */
    function test8()
    {
      this.eventSeq = [
        new invokerChecker(EVENT_REORDER, "t8_container")
      ];

      this.invoke = function test8_invoke()
      {
        var tree =
          { SECTION: [
            { PUSHBUTTON: [] },
            { ENTRY: [] },
            { ENTRY: [] },
            { ENTRY: [] }
          ] };
        testAccessibleTree("t8_container", tree);

        getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button");
      }

      this.finalCheck = function test8_finalCheck()
      {
        var tree =
          { SECTION: [
            { TEXT: [
              { ENTRY: [] },
              { ENTRY: [] },
              { ENTRY: [] }
            ] },
            { PUSHBUTTON: [] }
          ] };
        testAccessibleTree("t8_container", tree);
      }

      this.getID = function test8_getID()
      {
        return `Set ARIA owns on inaccessible span element that contains accessible children`;
      }
    }

    ////////////////////////////////////////////////////////////////////////////
    // Test
    ////////////////////////////////////////////////////////////////////////////

    //gA11yEventDumpToConsole = true;
    //enableLogging("tree,eventTree,verbose"); // debug stuff

    var gQueue = null;

    function doTest()
    {
      gQueue = new eventQueue();

      // test1
      gQueue.push(new changeARIAOwns());
      gQueue.push(new removeARIAOwns());
      gQueue.push(new setARIAOwns());
      gQueue.push(new addIdToARIAOwns());
      gQueue.push(new appendEl());
      gQueue.push(new removeEl());
      gQueue.push(new removeId());
      gQueue.push(new setId());

      // test2
      gQueue.push(new removeA11eteiner());

      // test3
      gQueue.push(new stealFromOtherARIAOwns());
      gQueue.push(new appendElToRecacheChildren());

      // test4
      gQueue.push(new showHiddenElement());

      // test5
      gQueue.push(new rearrangeARIAOwns(
        "t5_container", "t5_checkbox t5_radio t5_button",
        [ "t5_checkbox", "t5_radio", "t5_button" ],
        [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ]));
      gQueue.push(new rearrangeARIAOwns(
        "t5_container", "t5_radio t5_button t5_checkbox",
        [ "t5_radio", "t5_button" ],
        [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ]));

      gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span"));

      gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child"));

      gQueue.push(new test8());

      gQueue.invoke(); // SimpleTest.finish() will be called in the end
    }

    SimpleTest.waitForExplicitFinish();
    addA11yLoadEvent(doTest);

  </script>
</head>

<body>

  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test">
  </pre>

  <div id="t1_container" aria-owns="t1_checkbox t1_button">
    <div role="button" id="t1_button"></div>
    <div role="checkbox" id="t1_checkbox">
      <span id="t1_span">
        <div id="t1_subdiv"></div>
      </span>
    </div>
  </div>
  <div id="t1_group" role="group"></div>
  <div id="t1_grouptmp" role="group"></div>

  <div id="t2_container1" aria-owns="t2_owned"></div>
  <div id="t2_container2">
    <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
  </div>

  <div id="t3_container1" aria-owns="t3_child"></div>
  <div id="t3_child" role="checkbox"></div>
  <div id="t3_container2"></div>

  <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
  <div id="t4_container2">
    <div id="t4_child1" style="display:none" role="checkbox"></div>
    <div id="t4_child2" role="radio"></div>
  </div>

  <div id="t5_container">
    <div role="button" id="t5_button"></div>
    <div role="checkbox" id="t5_checkbox"></div>
    <div role="radio" id="t5_radio"></div>
  </div>

  <div id="t6_container" aria-owns="t6_fake">
    <span id="t6_span">hey</span>
  </div>
  <div id="t6_fake" role="group"></div>

  <div id="t7_container">
    <div id="t7_parent">
      <div id="t7_child"></div>
    </div>
  </div>

  <div id="t8_container">
    <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
  </div>
</body>

</html>
